package router

import (
	"context"
	"errors"
	"fmt"
	logger "gitee.com/zaper/private-logger"
	"gitee.com/zaper/service-center/model"
	"gitee.com/zaper/service-center/os_env"
	"gitee.com/zaper/service-center/service"
	"gitee.com/zaper/service-center/util"
	"gitee.com/zaper/software-framework/constant"
	reg "gitee.com/zaper/software-framework/register"
	"gitee.com/zaper/software-framework/router"
	"github.com/gin-gonic/gin"
	"github.com/goccy/go-json"
	clientv3 "go.etcd.io/etcd/client/v3"
	"io"
	"math/rand"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"sync"
)

const (
	minPort = 1024
	maxPort = 3027
)

var (
	heartbeatPool = sync.Pool{
		New: func() interface{} {
			return new(service.InstanceInfo)
		},
	}
)

func InitializeRegister() {
	go func() {
		port := getListenPort()
		model.PortChan <- port
		httpRouter := router.GetHttpRouter()
		httpRouter.RegisterRouteInstance(groups, routes, port)
		reg.InitializeRouter()
	}()
}

var groups = []string{"/api/v1/", "/api/v2"}

var routes = []router.Router{
	{groups[0], "service/register", constant.HttpPOST, []router.Option{register}},
	{groups[0], "service/discovery", constant.HttpPOST, []router.Option{discovery}},
	{groups[0], "service/discoveryOnce", constant.HttpPOST, []router.Option{discoveryOnce}},
}

func register(ctx *gin.Context) {
	serviceInstanceInfo := heartbeatPool.Get().(*service.InstanceInfo)
	defer heartbeatPool.Put(serviceInstanceInfo)
	err := parseRegisterParam(ctx, serviceInstanceInfo)
	if err != nil {
		logger.Errorf("parseParamToInstance err")
		util.NewRegisterResponse(10001, "", "register input param fail")
		return
	}
	clientEtcd, err := service.GetEtcdClient()
	if err != nil {
		logger.Errorf("Etcd err , err is %s", err)
		util.NewRegisterResponse(10002, "", "Etcd err")
		return
	}
	reg := service.NewRegisterAgent(clientEtcd,
		service.WithContext(context.Background()),
		service.WithTTl(30),
		service.WithMaxRetry(3),
		service.WithNameSpace(os.Getenv(os_env.AppNameSpaceKey)))

	router := reg.RegisterRoute(serviceInstanceInfo)
	var leaseID clientv3.LeaseID
	switch router {
	case service.Register:
		leaseID, err = reg.ServiceRegister(serviceInstanceInfo)
	case service.KeepLive:
		leaseID, err = reg.KeepAlive(serviceInstanceInfo)
	case service.RegisterErr:
		logger.Errorf("get register route fail")
		leaseID, err = -1, errors.New("get register route fail")
	}
	var resp *util.HttpResponseError
	if err == nil && leaseID != -1 {
		resp = util.NewRegisterResponse(0, strconv.FormatInt(int64(leaseID), 10),
			"register success")
	} else {
		resp = util.NewRegisterResponse(0, strconv.FormatInt(int64(leaseID), 10),
			"register fail")
	}
	util.HttpContextJSON(ctx, resp)
}

func parseRegisterParam(ctx *gin.Context, serviceInfo *service.InstanceInfo) error {
	if ctx.Request.Body == nil {
		logger.Errorf("Body body nil")
		return errors.New("body body nil")
	}
	body, err := io.ReadAll(ctx.Request.Body)
	if err != nil {
		logger.Errorf("ReadAll body is fail, err is %s", err)
		return errors.New("ReadAll body is fail")
	}
	err = json.Unmarshal(body, &serviceInfo)
	if err != nil {
		logger.Errorf("Unmarshal body is fail, err is %s", err)
		return errors.New("unmarshal body is fail")
	}
	return nil
}

func discovery(ctx *gin.Context) {
	serviceName := parseDiscoveryParam(ctx)
	if serviceName == "" {
		logger.Errorf("get service instance fail, serviceName is nil")
		resp := util.NewHttpResponseError(10001, "serviceName is nil")
		util.HttpContextJSON(ctx, resp)
		return
	}
	cliEtcd, err := service.GetEtcdClient()
	if err != nil {
		logger.Errorf("Etcd err, err is %s", err)
		resp := util.NewHttpResponseError(10002, "Etcd err")
		util.HttpContextJSON(ctx, resp)
		return
	}
	instances, err := service.NewDiscoveryAgent(cliEtcd).ServiceDiscover(serviceName)
	if err != nil {
		logger.Errorf("discover service fail, err is %s", err)
		resp := util.NewHttpResponseError(1, "discover service fail")
		util.HttpContextJSON(ctx, resp)
		return
	}
	logger.Infof("Discovery end, response is %s", instances)
	util.HttpContextJSON(ctx, util.NewHttpResponseError(0, instances))
}

func discoveryOnce(ctx *gin.Context) {
	serviceName := parseDiscoveryParam(ctx)
	if serviceName == "" {
		logger.Errorf("get service instance fail, serviceName is nil")
		resp := util.NewHttpResponseError(10001, "serviceName is nil")
		util.HttpContextJSON(ctx, resp)
		return
	}
	cliEtcd, err := service.GetEtcdClient()
	if err != nil {
		logger.Errorf("Etcd err, err is %s", err)
		resp := util.NewHttpResponseError(10002, "Etcd err")
		util.HttpContextJSON(ctx, resp)
		return
	}
	instance, err := service.NewDiscoveryAgent(cliEtcd).ServiceDiscoverOnce(serviceName)
	if err != nil {
		logger.Errorf("discover service fail, err is %s", err)
		resp := util.NewHttpResponseError(1, "discover service fail")
		util.HttpContextJSON(ctx, resp)
		return
	}
	util.HttpContextJSON(ctx, instance)
}

func parseDiscoveryParam(ctx *gin.Context) string {
	if ctx.Request.Body == nil {
		logger.Errorf("Body body nil")
		return ""
	}
	body, err := io.ReadAll(ctx.Request.Body)
	if err != nil {
		logger.Errorf("ReadAll body is fail, err is %s", err)
		return ""
	}

	var requestMap = make(map[string]string)
	err = json.Unmarshal(body, &requestMap)
	if err != nil {
		logger.Errorf("Unmarshal body is fail, err is %s", err)
		return ""
	}
	serviceName, ok := requestMap["serviceName"]
	if !ok {
		logger.Error("body is err, not contain serviceName")
		return ""
	}
	return serviceName
}

func getListenPort() string {
	for {
		port := rand.Intn(maxPort-minPort+1) + minPort
		cmd := exec.Command("netstat", "-ano")
		output, err := cmd.CombinedOutput()
		if err != nil {
			logger.Errorf("get prot fail, error is %s", err)
			return ""
		}
		rtArr := strings.Split(string(output), "\n")
		for _, line := range rtArr {
			if strings.Contains(line, fmt.Sprintf(":%d", port)) {
				break
			}
		}
		return strconv.Itoa(port)
	}
}
